/**********************************************************************
 *
 * Filename:    ping.c  v0.1
 * 
 * Description: a ping implementation.
 *
 * Notes:
 *		ping the host, and return the response time
 *		if(return<0) error;
 *		if(return==0) timeout;
 *      if(return>0) ok!
 *
 **********************************************************************/

#ifdef WIN32
#include <winsock.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/ip.h>

#include <netdb.h>
#include <stdlib.h>
#endif

#include "ping.h"

#define ICMP_ECHORSP	0
#define ICMP_ECHOREQ	8

#ifdef WIN32
#define GETUSEC(v) (v)
#define SETUSEC(v) (v)
#else
#define GETUSEC(v) ((v) / 1000)
#define SETUSEC(v) ((v) * 1000)
#endif


// IP Header -- RFC 791
typedef struct ip_header
{
	u_char			vi_hl;			// Version and IHL
	u_char			tos;			// Type Of Service
	short			len;			// Total Length
	short			id;				// Identification
	short			flag_off;		// Flags and Fragment Offset
	u_char			ttl;			// Time To Live
	u_char			protocol;		// Protocol
	u_short			cksum;		// Checksum
	struct	in_addr src;	// Internet Address - Source
	struct	in_addr dst;	// Internet Address - Destination
}ip_header_t;


// ICMP Header - RFC 792
typedef struct icmp_header
{
	u_char	type;			// Type
	u_char	code;			// Code
	u_short	cksum;		// Checksum
	u_short	id;				// Identification
	u_short	seq;			// Sequence
}icmp_header_t;


#define REQ_DATASIZE 32		// Echo Request Data size

// ICMP Echo Request
typedef struct echo_req
{
	icmp_header_t	icmphdr;
	struct timeval	time;
	char		data[REQ_DATASIZE];
}echo_req_t;


// ICMP Echo Reply
typedef struct echo_rsp
{
	ip_header_t	iphdr;
	echo_req_t	req;
	char		filler[256];
}echo_rsp_t;

//
// Mike Muuss' in_cksum() function
// and his comments from the original
// ping program
//
// * Author -
// *	Mike Muuss
// *	U. S. Army Ballistic Research Laboratory
// *	December, 1983

/*
 *			I N _ C K S U M
 *
 * Checksum routine for Internet Protocol family headers (C Version)
 *
 */
static u_short in_cksum(u_short *addr, int len)
{
	register int nleft = len;
	register u_short *w = addr;
	register u_short answer;
	register int sum = 0;

	/*
	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
	 *  we add sequential 16 bit words to it, and at the end, fold
	 *  back all the carry bits from the top 16 bits into the lower
	 *  16 bits.
	 */
	while( nleft > 1 )  {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if( nleft == 1 ) {
		u_short	u = 0;

		*(u_char *)(&u) = *(u_char *)w ;
		sum += u;
	}

	/*
	 * add back carry outs from top 16 bits to low 16 bits
	 */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return (answer);
}

static struct timeval gettime()
{
	struct timeval tv;
#ifdef WIN32
	u_long time;
	time=GetTickCount();
	tv.tv_sec=time/1000;
	tv.tv_usec=time%1000;
#else
	gettimeofday(&tv, (struct timezone *)NULL);
#endif
	//printf("current time %ds:%dms\n", tv.tv_sec, GETUSEC(tv.tv_usec));
	return tv;
}

int ping(const char *hostname, int TIMEOUT)
{
#ifdef WIN32
	SOCKET	s;
    WSADATA wsaData;
#else
	int	s;
#endif
	struct	hostent * hp;
	struct  sockaddr_in to;
	struct  sockaddr_in from;
	int		elapsed;

	static echo_req_t req;
	static int req_id = 1;
	static int req_seq = 1;

	struct timeval timeout, time;
	fd_set read_fds;

	echo_rsp_t rsp;
	int addr_len = sizeof(struct sockaddr_in);

//	int i;
	int ret;

#ifdef WIN32
	// Init WinSock
    if (WSAStartup(MAKEWORD(2,1), &wsaData)){
		return -1;//error
    }
#endif

	// Create a Raw socket
	s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
#ifdef WIN32
	if (s==INVALID_SOCKET) 
#else
	if (s<=0)
#endif
	{
		return -1;//error
	}

	// Lookup host
	hp = (struct hostent *)gethostbyname(hostname);

	// Setup destination socket address
	to.sin_addr.s_addr = *((u_long *) (hp->h_addr));
	to.sin_family = AF_INET;
	to.sin_port = 0;

	// Fill in echo request
	req.icmphdr.type	= ICMP_ECHOREQ;
	req.icmphdr.code	= 0;
	req.icmphdr.cksum	= 0;
	req.icmphdr.id		= req_id++;
	req.icmphdr.seq		= req_seq++;

	// Save tick count when sent
	req.time		= gettime();

	// Put data in packet and compute checksum
	req.icmphdr.cksum = 
		in_cksum((u_short *)&req, sizeof(echo_req_t));

	// Send the echo request  						 
	ret = sendto(s, (char *)&req, sizeof(echo_req_t), 0,
		(struct sockaddr *)&to, sizeof(struct sockaddr_in)); 
	if(ret<=0) {
		return -1; //error
	}

	// Use select() to wait for data to be received
	FD_ZERO(&read_fds);
	FD_SET(s, &read_fds);
	timeout.tv_sec = TIMEOUT/1000;
    timeout.tv_usec = SETUSEC(TIMEOUT%1000);
#ifdef WIN32
	ret = select(1, &read_fds, NULL, NULL, &timeout);
#else
	ret = select(s+1, &read_fds, NULL, NULL, &timeout);
#endif
	if (ret < 0){
		return -1;//error
	}else if (ret == 0){
		return 0; // timeout
	}

	// Receive the echo reply	
	ret = recvfrom(s, (char *)&rsp, sizeof(echo_rsp_t), 0, 
		(struct sockaddr *)&from, (socklen_t *)&addr_len);
	if(ret <= 0){
		return -1;//error
	}

	// Calculate elapsed time
	time = gettime();
	elapsed = (time.tv_sec - rsp.req.time.tv_sec)*1000 
		+ GETUSEC(time.tv_usec) - GETUSEC(rsp.req.time.tv_usec);
	
#ifdef WIN32
	if(closesocket(s)){
		return -1; //error
	}
	// Free WinSock
    WSACleanup();
#endif
	//return dwElapsed;
	if(elapsed==0) elapsed=1;

	return elapsed;
}

